package org.luxor.commons.core.utils.encrypt;


import org.apache.commons.codec.binary.Base64;
import org.luxor.commons.core.utils.IoUtils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA非对称加解密工具类 <非对称加密算法>
 * <p>
 * 常用于实现接口报文的数字签名生成和验证。
 * <p>
 *
 * @author HabenChan @date 2018/8/16
 */
public class RsaEncryptUtils {
    public static final String KEY_ALGORITHM = "RSA";
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    public static final int KEY_SIZE = 1024;
    private static final int MAX_ENCRYPT_BLOCK = 117;
    private static final int MAX_DECRYPT_BLOCK = 128;

    public static final String DEFAULT_PUBLIC_KEY_STR = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCqeoRPlzlf7Ie/zbj0Bab1HgrKxKMcImL56cB" +
            "k5TKZgs6yv3ti8Z3S7Yev3UcPbinVuJbsy9Trtd0nFu2PGfDC+vkmTZkpjl3BBfdn9BsBiobJJr3" +
            "uzCmtNTT0gYLbvefCQCLMkeUT18fz5sgUx+eruXWsayWqTfcJwx8/MXnrwIDAQAB";

    public static final String DEFAULT_PRIVATE_KEY_STR = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMKp6hE+XOV/sh7/NuPQFpvUeCsr" +
            "EoxwiYvnpwGTlMpmCzrK/e2LxndLth6/dRw9uKdW4luzL1Ou13ScW7Y8Z8ML6+SZNmSmOXcEF92f" +
            "0GwGKhskmve7MKa01NPSBgtu958JAIsyR5RPXx/PmyBTH56u5daxrJapN9wnDHz8xeevAgMBAAEC" +
            "gYBICE4IAXaoqd8En1X1aNvk3/NzreWYOFB9h8QBzlWhq67+G31pOG7BWC/VIu23/JLmwTzTx/N8" +
            "WNd7XByHy8Rs/9cIXOXvXlRLHbQMbP6WZoutfd5y+H/rODiFUrTwuf75Z1Yi5hctwPpkChuD4xmG" +
            "4kCCc8ezaqIuP8VTOaO/UQJBAO7Jru0msDoHBaIwZzQZ2uBMYIIhTn7Uxd8h9v15YQVmFzXJp5Ty" +
            "oKzFtnP9pyMoExNSvlq9FpNezhMQJLIt2WkCQQDQsgP5VH9E4ObPdJEbRjZk8GGuiqs99D1j//hs" +
            "BL/KG9Lwroq6ehRpI9XvMkj91LW4D9IK2CiJ6F+VYNyX6z1XAkBFqFP9HmIeK1o7W/9fNYOiFuW4" +
            "UFsDqLIoS1Iv0y64UnXG3RKj4rt4axy0bXRKTzBxXJvnAG4Lg4Z+DSGHUNSxAkEAq97/gNZC3iHu" +
            "VEnmuzSO1YBWoh1Xu/UqnfGP0L30iWlyxn6oh7zElHZL2Otry4fjhEubKw0sDa9PEMGtynuUjwJA" +
            "f3iktzjGPntpcBzdlg1LKkzSmRgmaf96r2lvhqE+yeoU5RoXaxTBz8ObyNCbXAJxLhPaTzZOtugD" +
            "N17bravOPQ==";

    /**
     * 1.1 RSA公钥加密
     *
     * @param plainText    明文
     * @param publicKeyStr 公钥串
     * @return encryptText 密文
     */
    public static byte[] encryptByPublicKey(byte[] plainText, String publicKeyStr) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            byte[] keyBytes = Base64.decodeBase64(publicKeyStr.getBytes());
            PublicKey publicKey = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(1, publicKey);
            int inputLen = plainText.length;
            int offSet = 0;

            for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_ENCRYPT_BLOCK) {
                byte[] cache;
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(plainText, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(plainText, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                ++i;
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("RSA公钥加密失败", e);
        } finally {
            IoUtils.closeQuietly(out);
        }
    }

    /**
     * 1.2 RSA私钥解密
     *
     * @param encryptText   密文
     * @param privateKeyStr 私钥串
     * @return plainText 明文字节
     */
    public static byte[] decryptByPrivateKey(byte[] encryptText, String privateKeyStr) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            byte[] keyBytes = Base64.decodeBase64(privateKeyStr.getBytes());
            PrivateKey privateKey = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(2, privateKey);
            int inputLen = encryptText.length;
            int offSet = 0;
            for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_DECRYPT_BLOCK) {
                byte[] cache;
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
                }

                out.write(cache, 0, cache.length);
                ++i;
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("RSA私钥解密失败", e);
        } finally {
            IoUtils.closeQuietly(out);
        }
    }


    /**
     * 2.1 RSA私钥加密
     *
     * @param plainText     明文
     * @param privateKeyStr 私钥
     * @return encryptText 密文
     */
    public static byte[] encryptByPrivateKey(byte[] plainText, String privateKeyStr) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            byte[] keyBytes = Base64.decodeBase64(privateKeyStr.getBytes());
            PrivateKey privateKey = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(1, privateKey);
            int inputLen = plainText.length;
            int offSet = 0;

            for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_ENCRYPT_BLOCK) {
                byte[] cache;
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(plainText, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(plainText, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                ++i;
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("RSA私钥加密失败", e);
        } finally {
            IoUtils.closeQuietly(out);
        }
    }

    /**
     * 2.2 RSA公钥解密
     *
     * @param encryptText  密文
     * @param publicKeyStr 公钥
     * @return plainText 明文
     */
    public static byte[] decryptByPublicKey(byte[] encryptText, String publicKeyStr) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            byte[] keyBytes = Base64.decodeBase64(publicKeyStr.getBytes());
            PublicKey publicKey = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(2, publicKey);
            int inputLen = encryptText.length;
            int offSet = 0;
            for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_DECRYPT_BLOCK) {
                byte[] cache;
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
                }

                out.write(cache, 0, cache.length);
                ++i;
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("RSA公钥解密失败", e);
        } finally {
            IoUtils.closeQuietly(out);
        }
    }


    /**
     * 3.1 基于‘私钥’，生成数字签名
     *
     * @param data          数据
     * @param privateKeyStr 私钥
     * @return sign 数字签名
     */
    public static byte[] sign(byte[] data, String privateKeyStr) {
        try {
            byte[] keyBytes = Base64.decodeBase64(privateKeyStr.getBytes());
            PrivateKey priK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
            Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initSign(priK);
            sig.update(data);
            return sig.sign();
        } catch (Exception e) {
            throw new RuntimeException("生成数字签名失败", e);
        }
    }

    /**
     * 3.2 基于‘公钥’，验证数字签名
     *
     * @param data         数据
     * @param sign         数字签名
     * @param publicKeyStr 公钥
     * @return 验证结果
     */
    public static boolean verify(byte[] data, byte[] sign, String publicKeyStr) {
        try {
            byte[] keyBytes = Base64.decodeBase64(publicKeyStr.getBytes());
            PublicKey pubK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
            Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initVerify(pubK);
            sig.update(data);
            return sig.verify(sign);
        } catch (Exception e) {
            throw new RuntimeException("验证数字签名失败", e);
        }
    }

    /**
     * 生成 私钥 & 公钥
     *
     * @return KeyPair
     */
    public static Keypair generateKeyPair() {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            keyPairGen.initialize(KEY_SIZE);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());
            String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());
            return new Keypair(privateKeyStr, publicKeyStr);
        } catch (Exception e) {
            throw new RuntimeException("生成RSA密钥对失败", e);
        }
    }

    static class Keypair implements Serializable {

        protected String privateKey;
        protected String publicKey;

        public Keypair(String privateKey, String publicKey) {
            this.privateKey = privateKey;
            this.publicKey = publicKey;
        }

        public String getPrivateKey() {
            return privateKey;
        }

        public void setPrivateKey(String privateKey) {
            this.privateKey = privateKey;
        }

        public String getPublicKey() {
            return publicKey;
        }

        public void setPublicKey(String publicKey) {
            this.publicKey = publicKey;
        }

        @Override
        public String toString() {
            return "Keypair{" +
                    "privateKey='" + privateKey + '\'' +
                    ", publicKey='" + publicKey + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        // 明文数据
        String plainTextStr = "hello word !!!";

        // 生成‘私钥&公钥’
        Keypair keypair = generateKeyPair();

        // 1.公钥加密&私钥解密
        byte[] encryptText = encryptByPublicKey(plainTextStr.getBytes(), keypair.getPublicKey());
        System.out.println("1.1 公钥加密结果: " + Base64.encodeBase64String(encryptText));
        byte[] plainText = decryptByPrivateKey(encryptText, keypair.getPrivateKey());
        System.out.println("1.2 私钥解密结果: " + new String(plainText));
        System.out.println();

        // 2.私钥加密&公钥解密
        encryptText = encryptByPrivateKey(plainTextStr.getBytes(), keypair.getPrivateKey());
        System.out.println("2.1 私钥加密结果: " + Base64.encodeBase64String(encryptText));
        plainText = decryptByPublicKey(encryptText, keypair.getPublicKey());
        System.out.println("2.2 公钥解密结果: " + new String(plainText));
        System.out.println();

        // 3.数字签名验证
        byte[] sign = sign(plainTextStr.getBytes(), DEFAULT_PRIVATE_KEY_STR);
        System.out.println("3.1 生成数字签名 sign: " + Base64.encodeBase64String(sign));
        boolean verify = verify(plainTextStr.getBytes(), sign, DEFAULT_PUBLIC_KEY_STR);
        System.out.println("3.2 验证数字签名 verify：" + verify);
    }
}
